//  Copyright (c) Microsoft Corporation.  All Rights Reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IdentityModel.Policy;

using System.IdentityModel.Claims;
using System.IdentityModel.Selectors;

namespace Saml20
{
    public class Saml20AuthzDecisionStatement : Saml20Statement
    {
        public enum DecisionType
        {
            Permit, 
            Deny,
            Indeterminate
        }

        string resource;
        DecisionType decisionType;
        readonly ImmutableCollection<Saml20Action> actions = new ImmutableCollection<Saml20Action>();

        // saml:Evidence is expanded out here.
        readonly ImmutableCollection<Saml20Assertion> assertionList = new ImmutableCollection<Saml20Assertion>();
        readonly ImmutableCollection<string> assertionIDRefList = new ImmutableCollection<string>();
        readonly ImmutableCollection<Uri> assertionURIRefList = new ImmutableCollection<Uri>();
        readonly ImmutableCollection<Saml20EncryptedAssertion> encryptedAssertionList = new ImmutableCollection<Saml20EncryptedAssertion>();

        bool isReadOnly = false;

        public Saml20AuthzDecisionStatement()
        {
        }

        public Saml20AuthzDecisionStatement(XmlReader reader, Saml20Serializer serializer, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            this.ReadXml(reader, serializer, keyInfoSerializer, outOfBandTokenResolver);
        }

        public override void MakeReadOnly()
        {
            if (!this.isReadOnly)
            {
                this.actions.MakeReadOnly();
                this.assertionList.MakeReadOnly();
                this.assertionIDRefList.MakeReadOnly();
                this.assertionURIRefList.MakeReadOnly();
                this.encryptedAssertionList.MakeReadOnly();
                this.isReadOnly = true;
            }
        }

        public string Resource
        {
            get { return this.resource; }
            set
            {
                if (this.isReadOnly)
                    throw new InvalidOperationException("The object is read-only.");

                if (String.IsNullOrEmpty(value))
                    throw new ArgumentException("Resource value cannot be null or empty string.");

                this.resource = value;
            }
        }

        public DecisionType Decision
        {
            get { return this.decisionType; }
            set
            {
                if (this.isReadOnly)
                    throw new InvalidOperationException("The object is read-only.");

                this.decisionType = value;
            }
        }

        public IList<Saml20Action> Actions
        {
            get
            {
                return this.actions;
            }
        }

        public IList<Saml20Assertion> EvidenceAssertions
        {
            get
            {
                return this.assertionList;
            }
        }

        public IList<string> EvidenceAssertionIDs
        {
            get
            {
                return this.assertionIDRefList;
            }
        }

        public IList<Uri> EvidenceAssertionURIs
        {
            get
            {
                return this.assertionURIRefList;
            }
        }

        public override IAuthorizationPolicy CreatePolicy(ClaimSet issuer, Saml20Assertion assertion, Saml20SecurityTokenAuthenticator tokenAuthenticator)
        {
            // This sample does not implement this method. Take a look at 
            // Saml20AttributeStatement.CreatePolicy, for an example of how this
            // method can be implemented.
            throw new Exception("The method or operation is not implemented.");
        }

        void CheckObjectValidity()
        {
            if (this.decisionType == null)
                throw new InvalidOperationException("saml:AuthzDecisionStatement must have a Decision specified.");
            if (String.IsNullOrEmpty(this.resource))
                throw new InvalidOperationException("saml:AuthzDecisionStatement must have a Resource specified.");
            if (this.Actions.Count == 0)
                throw new InvalidOperationException("saml:AuthzDecisionStatement must have at least on Action specified.");
        }

        public override void ReadXml(XmlReader reader, Saml20Serializer serializer, SecurityTokenSerializer keyInfoSerializer, SecurityTokenResolver outOfBandTokenResolver)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            this.resource = reader.GetAttribute(Saml20Constants.ResourceAttribute);
            System.ComponentModel.EnumConverter enumConverter = new System.ComponentModel.EnumConverter(typeof(DecisionType));
            this.decisionType = (DecisionType)enumConverter.ConvertFromString(reader.GetAttribute(Saml20Constants.DecisionAttribute));

            reader.Read();

            while (reader.IsStartElement(Saml20Constants.Action, Saml20Constants.Namespace))
            {
                actions.Add(new Saml20Action(reader, serializer, keyInfoSerializer, outOfBandTokenResolver));
            }

            if ((actions == null) || (actions.Count == 0))
                throw new SecurityTokenException("<saml:AuthZStatement> must contain at least one <saml:Action> element.");

            if (reader.IsStartElement(Saml20Constants.Evidence, Saml20Constants.Namespace))
            {
                reader.ReadStartElement();
                while (reader.IsStartElement())
                {
                    if (reader.IsStartElement(Saml20Constants.Assertion, Saml20Constants.Namespace))
                    {
                        if ((this.encryptedAssertionList.Count != 0) || (this.assertionIDRefList.Count != 0) || (this.assertionURIRefList.Count != 0))
                            throw new SecurityTokenException("saml:Evidence has more than one element type as the child element.");

                        this.assertionList.Add(serializer.LoadAssertion(reader, keyInfoSerializer, outOfBandTokenResolver));
                    }
                    else if (reader.IsStartElement(Saml20Constants.EncryptedAssertion, Saml20Constants.Namespace))
                    {
                        if ((this.assertionList.Count != 0) || (this.assertionIDRefList.Count != 0) || (this.assertionURIRefList.Count != 0))
                            throw new SecurityTokenException("saml:Evidence has more than one element type as the child element.");

                        this.encryptedAssertionList.Add(serializer.LoadEncryptedAssertion(reader, keyInfoSerializer, outOfBandTokenResolver));
                    }
                    else if (reader.IsStartElement(Saml20Constants.AssertionIDRef, Saml20Constants.Namespace))
                    {
                        if ((this.assertionList.Count != 0) || (this.encryptedAssertionList.Count != 0) || (this.assertionURIRefList.Count != 0))
                            throw new SecurityTokenException("saml:Evidence has more than one element type as the child element.");

                        this.assertionIDRefList.Add(reader.ReadElementString());
                    }
                    else if (reader.IsStartElement(Saml20Constants.AssertioURIRef, Saml20Constants.Namespace))
                    {
                        if ((this.encryptedAssertionList.Count != 0) || (this.assertionIDRefList.Count != 0) || (this.assertionList.Count != 0))
                            throw new SecurityTokenException("saml:Evidence has more than one element type as the child element.");

                        this.assertionURIRefList.Add(new Uri(reader.ReadElementString()));
                    }
                    else
                    {
                        throw new SecurityTokenException("Unknown child element inside saml:Evidence element.");
                    }
                }

                if ((this.assertionList == null || this.assertionList.Count == 0) &&
                    (this.assertionIDRefList == null || this.assertionIDRefList.Count == 0) &&
                    (this.assertionURIRefList == null || this.assertionURIRefList.Count == 0))
                    throw new SecurityTokenException("<saml:Evidence> requires at least on Assertion or AssertionId or AssertionURI to be specified.");
            }

            reader.ReadEndElement();
        }

        public override void WriteXml(XmlWriter writer, SecurityTokenSerializer tokenSerializer)
        {
            CheckObjectValidity();

            if (writer == null)
                throw new ArgumentNullException("writer");

            writer.WriteStartElement(Saml20Constants.AuthzDecisionStatement, Saml20Constants.Namespace);

            writer.WriteAttributeString(Saml20Constants.ResourceAttribute, this.resource);
            writer.WriteAttributeString(Saml20Constants.DecisionAttribute, this.decisionType.ToString());

            for (int i = 0; i < this.actions.Count; ++i)
            {
                this.actions[i].WriteXml(writer, tokenSerializer);
            }

            writer.WriteStartElement(Saml20Constants.Evidence, Saml20Constants.Namespace);
            if (this.assertionList.Count > 0)
            {
                for (int i = 0; i < this.assertionList.Count; ++i)
                {
                    this.assertionList[i].WriteXml(writer, tokenSerializer);
                }
            }
            else if (this.encryptedAssertionList.Count > 0)
            {
                for (int i = 0; i < this.encryptedAssertionList.Count; ++i)
                {
                    this.encryptedAssertionList[i].WriteXml(writer, tokenSerializer);
                }
            }
            else if (this.assertionIDRefList.Count > 0)
            {
                for (int i = 0; i < this.assertionIDRefList.Count; ++i)
                {
                    writer.WriteStartElement(Saml20Constants.AssertionIDRef, Saml20Constants.Namespace);
                    writer.WriteString(this.assertionIDRefList[i]);
                    writer.WriteEndElement();
                }
            }
            else if (this.assertionURIRefList.Count > 0)
            {
                for (int i = 0; i < this.assertionURIRefList.Count; ++i)
                {
                    writer.WriteStartElement(Saml20Constants.AssertioURIRef, Saml20Constants.Namespace);
                    writer.WriteString(this.assertionURIRefList[i].AbsoluteUri);
                    writer.WriteEndElement();
                }
            }
            writer.WriteEndElement();

            writer.WriteEndElement();
        }
    }
}
